package com.icoding.fw.dao.impl;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.log4j.Logger;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Property;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate4.support.HibernateDaoSupport;

import com.icoding.fw.dao.inf.CommonDao;
import com.icoding.fw.utils.HqlQuery;
import com.icoding.fw.utils.NativeQuery;

public abstract class CommonDaoImpl<T> extends HibernateDaoSupport implements
		CommonDao<T> {
	private Class<T> entityClass;

	private final static Logger LOGGER = Logger.getLogger(CommonDaoImpl.class);

	@Autowired
	public void setSession(SessionFactory sessionFactory) {
		this.setSessionFactory(sessionFactory);
	}

	@SuppressWarnings("unchecked")
	public CommonDaoImpl() {
		entityClass = (Class<T>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
	}

	@Override
	public List<T> loadAll() {
		return getHibernateTemplate().loadAll(entityClass);
	}

	@Override
	public boolean save(T bean) {
		Boolean flag = false;
		try {
			getHibernateTemplate().save(bean);
			flag = true;
		} catch (Exception e) {
			LOGGER.error("Have an error:", e);
			flag = false;
		}
		return flag;
	}

	@Override
	public boolean update(T bean) {
		Boolean flag = false;
		try {
			getHibernateTemplate().update(bean);
			flag = true;
		} catch (Exception e) {
			LOGGER.error("Have an error:", e);
			flag = false;
		}
		return flag;
	}

	@Override
	public boolean delete(Serializable id) {
		Boolean flag = false;
		try {
			T t = get(id);
			getHibernateTemplate().delete(t);
			flag = true;
		} catch (Exception e) {
			LOGGER.error("Have an error:", e);
			flag = false;
		}
		return flag;
	}

	@Override
	public boolean saveOrUpdate(T bean) {
		Boolean flag = false;
		try {
			getHibernateTemplate().saveOrUpdate(bean);
			flag = true;
		} catch (Exception e) {
			LOGGER.error("Have an error:", e);
			flag = false;
		}
		return flag;
	}

	@Override
	public boolean merge(T bean) {
		Boolean flag = false;
		try {
			getHibernateTemplate().merge(bean);
			flag = true;
		} catch (Exception e) {
			LOGGER.error("Have an error:", e);
			flag = false;
		}
		return flag;
	}

	@Override
	public T get(Serializable id) {
		T o = (T) getHibernateTemplate().get(entityClass, id);
		return o;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public List getListByCriteria(DetachedCriteria detachedCriteria) {
		return (List<T>) getHibernateTemplate()
				.findByCriteria(detachedCriteria);

	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public List getListByCriteria(DetachedCriteria detachedCriteria,
			int offset, int size) {
		return (List<T>) getHibernateTemplate().findByCriteria(
				detachedCriteria, offset, size);
	}

	/**
	 * I will support two kind of operator 1 - Like Operator 2 - Equal Operator
	 */
	@SuppressWarnings("unchecked")
	@Override
	public boolean checkDuplicate(Class<T> clazz, boolean isEqual,
			String columnName, Object value) {
		Boolean flag = false;
		try {
			List<T> list = new ArrayList<T>();
			if (isEqual) {
				list = getListByCriteria(DetachedCriteria.forClass(clazz).add(
						Property.forName(columnName).eq(value)));
			} else {
				list = getListByCriteria(DetachedCriteria.forClass(clazz).add(
						Property.forName(columnName).like(value)));
			}
			if (!list.isEmpty()) {
				flag = true;
			}
		} catch (Exception e) {
			LOGGER.error("Error at checkDuplicate", e);
		}
		return flag;
	}

	/**
	 * I will support two kind of operator 1 - Like Operator 2 - Equal Operator
	 */
	@SuppressWarnings("unchecked")
	@Override
	public List<T> getListByColumn(Class<T> clazz, boolean isEqual,
			String columnName, Object value) {
		List<T> list = new ArrayList<T>();
		try {
			if (isEqual) {
				list = getListByCriteria(DetachedCriteria.forClass(clazz).add(
						Property.forName(columnName).eq(value)));
			} else {
				list = getListByCriteria(DetachedCriteria.forClass(clazz).add(
						Property.forName(columnName).like(value)));
			}
		} catch (Exception e) {
			LOGGER.error("Error at getListByColumn", e);
		}
		return list;
	}

	/**
	 * Map<String, Object> map String is property of hibernate class - 1 column
	 * Object
	 */
	@SuppressWarnings("unchecked")
	@Override
	public List<T> getListByMoreColumns(Class<T> clazz, Map<String, Object> map) {
		List<T> list = new ArrayList<T>();
		DetachedCriteria detachedCriteria = null;
		try {
			detachedCriteria = DetachedCriteria.forClass(clazz);
			for (Entry<String, Object> entry : map.entrySet()) {
				detachedCriteria.add(Property.forName(entry.getKey()).eq(
						entry.getValue()));
			}
		} catch (Exception e) {
			LOGGER.error("Error at getListByColumns", e);
		} finally {
			list = getListByCriteria(detachedCriteria);
		}
		return list;
	}

	@SuppressWarnings("unchecked")
	@Override
	public boolean checkDuplicateMoreColumns(Class<T> clazz,
			Map<String, Object> map) {
		List<T> list = new ArrayList<T>();
		DetachedCriteria detachedCriteria = null;
		try {
			detachedCriteria = DetachedCriteria.forClass(clazz);
			for (Entry<String, Object> entry : map.entrySet()) {
				detachedCriteria.add(Property.forName(entry.getKey()).eq(
						entry.getValue()));
			}
			list = getListByCriteria(detachedCriteria);
			if (list.size() != 0) {
				return true;
			}
		} catch (Exception e) {
			LOGGER.error("Error at checkDuplicateMoreColumns", e);
		}
		return false;
	}

	// Get Session
	public Session getSession() {
		return currentSession();
	}

	// Call Native Query
	public NativeQuery callNativeQuery() {
		return new NativeQuery(getSession());
	}

	// Call HQL Query
	public HqlQuery callHQLQuery() {
		return new HqlQuery(getSession());
	}

	@Override
	public int saveAndGetId(T bean) {
		int id = 0;
		id = (Integer) getHibernateTemplate().save(bean);
		return id;
	}
}
